"""
====================================================================
Copyright (C) 2012-2013 Eric Blond

This file is part of Neptune.

Neptune is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Neptune is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Neptune.  If not, see <http://www.gnu.org/licenses/>.
====================================================================

Created on Dec 8, 2012

@author: Eric
"""

import datetime
import threading
import heapq
from . import page
from . import reactor
from . import utils

#######

class Item(object):
    
    NEXT_ID = 0
    
    def __init__(self, delay):
        self.utc = datetime.datetime.utcnow() + delay
        self.id = Item.NEXT_ID
        Item.NEXT_ID += 1
    
    def __cmp__(self, other):
        return cmp(self.utc, other.utc)

    def delay(self):
        delay = self.utc - datetime.datetime.utcnow()
        if delay < datetime.timedelta():
            return datetime.timedelta()
        else:
            return delay

#######

class SchedulerHandler(page.Page):

    Heap    = []
    Mapping = {}
    Timer   = None
    
    def _timedelta(self, delay):
        if type(delay) in (int, float):
            return datetime.timedelta(seconds = delay)
        else:
            return delay

    def book(self, delay, callback):
        item = Item(self._timedelta(delay))
        with (~self).Heap as q:
            heapq.heappush(q, item)
        self.Mapping[item.id] = callback
        self.dispatch()
    
    def dispatch(self):
        while self.Heap:
            item = self.Heap[0]
            if item.delay() > datetime.timedelta():
                break
            with (~self).Heap as q:
                heapq.heappop(q)
            try:
                self.Mapping[item.id]()
            except Exception:
                utils.LOGGER.warning('Callback raised an exception!', exc_info = True)
            del self.Mapping[item.id]
        if self.Heap:
            item = self.Heap[0]
            if self.Timer:
                self.Timer.cancel()
                self.Timer = None
            self.Timer = threading.Timer(item.delay().total_seconds(),
                                         reactor.reactor().lift(self.dispatch, sync = False))
            self.Timer.start()
        elif self.Timer:
            self.Timer.cancel()
            self.Timer = None

#######

class Scheduler(page.Page):
    
    _Reactor = None
    _Handler = None
    
    @page.cell
    def _Handler(self):
        return self._Reactor.handler(sync = False)

    def start(self):
        self._Reactor = reactor.Reactor(SchedulerHandler)
        self._Reactor.start()

    def stop(self):
        self._Reactor.stop()
        self._Reactor = None
    
    def book(self, delay, callback):
        lifted = reactor.reactor().lift(callback, sync = False)
        self._Handler.book(delay, lifted)
